﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Lidgren.Network;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using AwesomeGame.Weapons;

namespace AwesomeGame.Networking
{
    public class Server : IServer 
    {
        public delegate void SayEvent(string message);
        public event SayEvent MessageReceived;

        private NetServer _server;
        private int _port;

        private List<Player> _players;

        public Server(int port)
        {
            _port = port;
            _players = new List<Player>();
        }

        public void Start()
        {
            MessageReceived("Starting server...");

            NetPeerConfiguration configuration = new NetPeerConfiguration("AwesomeGame");
            configuration.Port = _port;
            configuration.MaximumConnections = 4;

            _server = new NetServer(configuration);
            _server.Start();

            MessageReceived("Server started.");
        }

        public void AnnouncePlayerJoined(Player player)
        {
            NetOutgoingMessage outgoingMessage = _server.CreateMessage();

            outgoingMessage.Write((int)DataMessageTypes.JOIN);
            CommonReadsAndWrites.WritePlayer(player, ref outgoingMessage);

            _server.SendToAll(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
        }

        public void AnnounceRemoveProjectileEvent(Guid projectileId)
        {
            NetOutgoingMessage outgoingMessage = _server.CreateMessage();

            outgoingMessage.Write((int)DataMessageTypes.REMOVE_PROJECTILE);
            CommonReadsAndWrites.WriteGuid(projectileId, ref outgoingMessage);

            _server.SendToAll(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
        }

        public void AnnounceMoveProjectileEvent(Guid projectileId, Vector2 position)
        {
            NetOutgoingMessage outgoingMessage = _server.CreateMessage();

            outgoingMessage.Write((int)DataMessageTypes.PROJECTILE_MOVED);
            CommonReadsAndWrites.WriteGuid(projectileId, ref outgoingMessage);
            CommonReadsAndWrites.WriteVector(position, ref outgoingMessage);

            _server.SendToAll(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
        }

        public void AnnouncePlayerDeath(long playerWhoDied, long playerWhoKilledThem)
        {
            NetOutgoingMessage outgoingMessage = _server.CreateMessage();

            outgoingMessage.Write((int)DataMessageTypes.PLAYER_DIED);
            outgoingMessage.Write(playerWhoDied);
            outgoingMessage.Write(playerWhoKilledThem);

            _server.SendToAll(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
        }

        public void AnnounceMessage(string message)
        {
            NetOutgoingMessage outgoingMessage =_server.CreateMessage();

            CommonReadsAndWrites.WriteMessage(message, ref outgoingMessage);

            _server.SendToAll(outgoingMessage, NetDeliveryMethod.ReliableOrdered);
        }

        public void AnnounceTankPosition(Vector2 position, float rotation, long tankId, NetConnection tankOwner)
        {
            NetOutgoingMessage outgoingMessage = _server.CreateMessage();

            outgoingMessage.Write((int)DataMessageTypes.SET_TANK_POSITION);
            CommonReadsAndWrites.WriteVector(position, ref outgoingMessage);
            outgoingMessage.Write(rotation);
            outgoingMessage.Write(tankId);
            _server.SendToAll(outgoingMessage, tankOwner, NetDeliveryMethod.ReliableOrdered, 0);
        }

        public void AnnounceTankFired(Vector2 position, float rotation, WeaponType weapon, long tankId, Guid firedEventId, NetConnection tankOwner)
        {
            NetOutgoingMessage outgoingMessage = _server.CreateMessage();

            outgoingMessage.Write((int)DataMessageTypes.TANK_FIRED);
            CommonReadsAndWrites.WriteVector(position, ref outgoingMessage);
            outgoingMessage.Write(rotation);
            outgoingMessage.Write((int)weapon);
            outgoingMessage.Write(tankId);
            CommonReadsAndWrites.WriteGuid(firedEventId, ref outgoingMessage);
            _server.SendToAll(outgoingMessage, tankOwner, NetDeliveryMethod.ReliableOrdered, 0);
        }

        public void AnnounceBombDropped(Vector2 position, NetConnection bombOwner)
        {
            NetOutgoingMessage outgoingMessage = _server.CreateMessage();

            outgoingMessage.Write((int)DataMessageTypes.BOMB_DROPPED);
            CommonReadsAndWrites.WriteVector(position, ref outgoingMessage);

            _server.SendToAll(outgoingMessage, bombOwner, NetDeliveryMethod.ReliableOrdered, 0);
        }

        public void SendIdToPlayer(long id, NetConnection playerClient)
        {
            NetOutgoingMessage outgoingMessage = _server.CreateMessage();
            outgoingMessage.Write((int)DataMessageTypes.SET_PLAYER_ID);
            outgoingMessage.Write(id);

            _server.SendMessage(outgoingMessage, playerClient, NetDeliveryMethod.ReliableOrdered);
        }

        public void SendFullPlayerListToPlayer(List<Player> players, NetConnection playerClient)
        {
            NetOutgoingMessage outgoingMessage = _server.CreateMessage();
            CommonReadsAndWrites.WriteListOfPlayers(players, ref outgoingMessage);
            _server.SendMessage(outgoingMessage, playerClient, NetDeliveryMethod.ReliableOrdered);
        }

        private void ReceivedMessageFromClient(NetIncomingMessage incomingMessage)
        {
            string message = CommonReadsAndWrites.ReadMessage(incomingMessage);
            AnnounceMessage(message);
        }

        private void ReceivedRemoveProjectileEventFromClient(NetIncomingMessage incomingMessage)
        {
            Guid projectileId = CommonReadsAndWrites.ReadGuid(incomingMessage);
            AnnounceRemoveProjectileEvent(projectileId);
        }

        private void ReceivedMoveProjectileEventFromClient(NetIncomingMessage incomingMessage)
        {
            Guid projectileId = CommonReadsAndWrites.ReadGuid(incomingMessage);
            Vector2 position = CommonReadsAndWrites.ReadVector(incomingMessage);
            AnnounceMoveProjectileEvent(projectileId, position);
        }

        private void ReceivedPlayerDeathFromClient(NetIncomingMessage incomingMessage)
        {
            long playerWhoDied = incomingMessage.ReadInt64();
            long playerWhoKilledThem = incomingMessage.ReadInt64();
            AnnouncePlayerDeath(playerWhoDied, playerWhoKilledThem);
        }

        private void ReceivedJoinAnnouncementFromClient(NetIncomingMessage incomingMessage)
        {
            Player player = CommonReadsAndWrites.ReadPlayer(incomingMessage);
            player.Id = incomingMessage.SenderConnection.RemoteUniqueIdentifier;
            SendIdToPlayer(player.Id, incomingMessage.SenderConnection);
            _players.Add(player);
            AnnouncePlayerJoined(player);
            SendFullPlayerListToPlayer(_players, incomingMessage.SenderConnection);
            
        }

        private void ReceivedTankPositionFromClient(NetIncomingMessage incomingMessage)
        {
            Vector2 position = CommonReadsAndWrites.ReadVector(incomingMessage);
            float rotation = incomingMessage.ReadSingle();
            long tankId = incomingMessage.ReadInt64();

            AnnounceTankPosition(position, rotation, tankId, incomingMessage.SenderConnection);
        }

        private void ReceivedTankFiredEventFromClient(NetIncomingMessage incomingMessage)
        {
            Vector2 position = CommonReadsAndWrites.ReadVector(incomingMessage);
            float rotation = incomingMessage.ReadSingle();
            WeaponType weapon = (WeaponType)incomingMessage.ReadInt32();
            long tankId = incomingMessage.ReadInt64();
            Guid firedEventId = CommonReadsAndWrites.ReadGuid(incomingMessage);

            AnnounceTankFired(position, rotation, weapon, tankId, firedEventId, incomingMessage.SenderConnection);
        }

        private void ReceivedBombDroppedEventFromClient(NetIncomingMessage incomingMessage)
        {
            Vector2 position = CommonReadsAndWrites.ReadVector(incomingMessage);

            AnnounceBombDropped(position, incomingMessage.SenderConnection);
        }

        public void ParseNetworkInput(NetIncomingMessage message, bool isServer)
        {
            DataMessageTypes dataType = (DataMessageTypes)message.ReadInt32();

            if (dataType == DataMessageTypes.SAY)
                ReceivedMessageFromClient(message);
            else if (dataType == DataMessageTypes.JOIN)
                ReceivedJoinAnnouncementFromClient(message);
            else if (dataType == DataMessageTypes.SET_TANK_POSITION)
                ReceivedTankPositionFromClient(message);
            else if (dataType == DataMessageTypes.TANK_FIRED)
                ReceivedTankFiredEventFromClient(message);
            else if (dataType == DataMessageTypes.REMOVE_PROJECTILE)
                ReceivedRemoveProjectileEventFromClient(message);
            else if (dataType == DataMessageTypes.BOMB_DROPPED)
                ReceivedBombDroppedEventFromClient(message);
            else if (dataType == DataMessageTypes.PLAYER_DIED)
                ReceivedPlayerDeathFromClient(message);
            else if (dataType == DataMessageTypes.PROJECTILE_MOVED)
                ReceivedMoveProjectileEventFromClient(message);
        }

        public void Update()
        {
            NetIncomingMessage serverMessage;
            while ((serverMessage = _server.ReadMessage()) != null)
            {
                switch (serverMessage.MessageType)
                {
                    case NetIncomingMessageType.VerboseDebugMessage:
                    case NetIncomingMessageType.DebugMessage:
                    case NetIncomingMessageType.WarningMessage:
                    case NetIncomingMessageType.ErrorMessage:
                        Debug.WriteLine(serverMessage.ReadString());
                        break;
                    case NetIncomingMessageType.Data:
                        ParseNetworkInput(serverMessage, true);
                        break;
                    default:
                        break;
                }
                _server.Recycle(serverMessage);
            }
        }
    }
}
